/**
 * 配置
 */
package org.zmhhxl.sample3.oauth2.a.configure;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.zmhhxl.api.auth.RemoteThirdPartyService;
import org.zmhhxl.api.auth.RemoteUserInfoService;
import org.zmhhxl.api.auth.domain.entity.Role;
import org.zmhhxl.api.auth.domain.entity.ThirdPartyUser;
import org.zmhhxl.api.auth.domain.entity.UserInfo;
import org.zmhhxl.common.core.constants.SecurityConstants;
import org.zmhhxl.common.core.utils.GeneralFunction;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Customize the access token and add user role information
 *
 *  无论哪种登录方式，登录成功后都要调用此类
 *
 *  接口允许你在JWT生成之前修改JwtEncodingContext，
 *  从而可以添加或修改JWT中的claims。
 *
 * @author: ReLive
 * @date: 2022/7/7 8:24 下午
 */
//@Configuration(proxyBeanMethods = false)
@Slf4j
//@ComponentScan(basePackages = {"org.zmhhxl.api.auth"})
// 转移到FederatedIdentityIdTokenCustomizer
public class AccessTokenCustomizerConfig {

   // 转移到FederatedIdentityIdTokenCustomizer

   /*
    * @author zmhhxl
    * @description TODO
    * @date 2024/10/4
    * 在此可添加服务类 OidcUserInfoService userInfoService
    * 在JwtAccessTokenConverter或TokenEnhancer中完成的
    * */

   private final RemoteUserInfoService userInfoService;

   private final RemoteThirdPartyService thirdPartyUserService;

   @Autowired
   public AccessTokenCustomizerConfig(RemoteUserInfoService userInfoService, RemoteThirdPartyService thirdPartyUserService) {
      this.userInfoService = userInfoService;
      this.thirdPartyUserService = thirdPartyUserService;
   }


   //@Bean
   public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(/*UserInfoService userInfoService, ThirdPartyUserService thirdPartyUserService*/) {
      return (context) -> {

         //log.info("sub={}, name={}", context.getPrincipal().getName(), context.getPrincipal().getPrincipal());

//         if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
//            context.getClaims().claims((claim) -> {
////               Set<String> roles = //AuthorityUtils.authorityListToSet(
////                     context.getPrincipal().getAuthorities()
////                           .stream()
////                           .map(c -> c.getAuthority().replaceFirst("^ROLE_", ""))
////                           //.map(GrantedAuthority::getAuthority)
////                           .collect(Collectors.toSet());
//
//               claim.put("role", context.getPrincipal().getAuthorities().stream()
//                           //.map(c->c.getAuthority().replaceFirst("^ROLE_", ""))
//                     .map(grantedAuthority -> grantedAuthority.getAuthority())
//                    .collect(Collectors.toSet()));
//            });
//         }
         //=======================================
         // 检查登录用户信息是不是UserDetails，排除掉没有用户参与的流程
         // 第三方登录方时UserDetails为null
         if (context.getPrincipal().getPrincipal() instanceof UserDetails user) {

            // 获取申请的scopes
            Set<String> scopes = context.getAuthorizedScopes();
            // 获取用户的权限 或context.getPrincipal().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
            Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
            // 提取权限并转为字符串
            Set<String> authoritySet = Optional.ofNullable(authorities).orElse(Collections.emptyList()).stream()
                  // 获取权限字符串
                  .map(GrantedAuthority::getAuthority)
                  // 去重
                  .collect(Collectors.toSet());  //Collectors.toList()

            // 合并scope与用户信息
            authoritySet.addAll(scopes);

            JwtClaimsSet.Builder claims = context.getClaims();
            // 将权限信息放入jwt的claims中（也可以生成一个以指定字符分割的字符串放入）
            claims.claim(SecurityConstants.AUTHORITIES_KEY, authoritySet);
            log.info("token UserDetails aaaaa {}", authoritySet);   // 他在每次userInfo调用时都会执行，他会登录后反复执行
            // 放入其它自定内容
            // 角色、头像...

            // 第三方登录,非本地口令登陆
         } else {
            // 获取申请的scopes
            Set<String> scopes1 = context.getAuthorizedScopes();

            Collection<? extends GrantedAuthority> authorities1 = context.getPrincipal().getAuthorities();

            Set<String> authoritySet1 = Optional.ofNullable(authorities1).orElse(Collections.emptyList()).stream()
                  .map(GrantedAuthority::getAuthority)
                  .collect(Collectors.toSet());

            // 合并scope与用户信息
            authoritySet1.addAll(scopes1);

            authoritySet1.add("ROLE_ADMIN1");  //在此添加授权信息

            // OAuth2第三方登录
            if (context.getPrincipal().getPrincipal() instanceof OAuth2User) {
               OAuth2User oAuth2User = (OAuth2User) context.getPrincipal().getPrincipal();
               log.info("第三方登录用户信息01：aaaaa {}", oAuth2User);
               //String name = (String) oAuth2User.getAttribute("name");

               Object provider = GeneralFunction.mapFuzzyMatch(oAuth2User.getAttributes(), "url", 0.9).entrySet().stream().findFirst().orElse(null).getValue();
               // 获取第三方登录的id
               String name = oAuth2User.getName();

               if ((provider != null) && (name != null)) {
                  String domainUrl = GeneralFunction.extractDomain((String) provider);

                  ThirdPartyUser thirdPartyUser = thirdPartyUserService.findByThirdPartyIdAndThirdPartyDomain(name, domainUrl).getData().orElse(null);
                  //第一次使用第三方登录
                  if (thirdPartyUser != null) {
                     // 确保 getUserInfos() 不为空
                     Long userIdOptional = thirdPartyUserService.findUserIdById(thirdPartyUser.getId()).getData().orElse(null);
                     Optional<UserInfo> userInfoOptional = Optional.empty();
                     if (userIdOptional != null) {
                        // 存在则创建用户信息
                        // 获取用户信息
                        userInfoOptional = userInfoService.findById(userIdOptional);
                     }

//                     if (!userInfo.isPresent()) {
//                        // 创建用户信息
//                        userInfo = Optional.of(userInfoService.save(new UserInfo()));
//                     }
                     //Optional<UserInfo> userInfoOptional = thirdPartyUser.getUserInfos().stream().findFirst();
                     log.info("第三方登录用户信息03：aaaaa {}", userInfoOptional);
                     if (userInfoOptional.isPresent()) {
                        UserInfo userInfo1 = userInfoOptional.get();
                        log.info("第三方登录用户信息02：aaaaa {}", userInfo1);

                        // 获取用户角色信息
                        // 已经有关联的角色
                        Set<Role> roles = userInfo1.getAuthorities();
                        // 获取用户角色信息
                        Set<String> roleNames = roles.stream().map(Role::getRoleName).collect(Collectors.toSet());
                        authoritySet1.add("zmhhxl_role");  //在此添加授权信息
                        // 合并scope与用户信息
                        authoritySet1.addAll(roleNames);
                        // 添加用户角色信息
                        context.getClaims().claim(SecurityConstants.AUTHORITIES_KEY, authoritySet1);

                        // 对未关联情况的处理
                        //context.getClaims().claim(SecurityConstants.AUTHORITIES_KEY, authoritySet1);

                     }

                  } else {
                     // 未关联第三方账号，应该抛出意外
                     //throw(new RuntimeException("第三方登录用户信息未关联本地账号"));
                     //赋予基本权限
                     context.getClaims().claim(SecurityConstants.AUTHORITIES_KEY, authoritySet1);
                  }
               }

            }

            // 添加用户角色信息
            //context.getClaims().claim(SecurityConstants.AUTHORITIES_KEY, authoritySet1);

         }

         // 在运行时，会调用此bean
         // 联合认证时,2种认证都会通过此方法
         if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
            log.info("确认全都通过 token1 oidc aaaaa ===========");
            // 2种认证都会通过此方法
            // 第三方登录时，获取第三方登录用户的信息，本地登录时是空的
            Map<String, Object> thirdPartyClaims = extractClaims(context.getPrincipal());
            //如果不是第三方登录，则thirdPartyClaims为空
            if (!thirdPartyClaims.isEmpty()) {
               // Add id_token claims to access_token claims
               // OAuth2User user = (OAuth2User) context.getPrincipal().getPrincipal();
               log.info("token3 oidc aaaaa {}", context.getPrincipal().getPrincipal());

               // getClaims()包含sub
               context.getClaims().claims(existingClaims -> {
                  // Remove conflicting claims set by this authorization server
                  // 删除此授权服务器设置的冲突声明,因为第三方登录时，会访问此2次，
                  existingClaims.keySet().forEach(thirdPartyClaims::remove);

                  // Remove standard id_token claims that could cause problems with clients
                  // 删除可能导致客户端出现问题的标准id_token声明
                  ID_TOKEN_CLAIMS.forEach(thirdPartyClaims::remove);

                  // Add all other claims directly to id_token
                  // 将所有其他声明直接添加到id_token中
                  existingClaims.putAll(thirdPartyClaims);

               });
            }
            //包含全部
            log.info("token2 oidc oauth aaaaa {}", context.getClaims().build().getClaims().entrySet().stream().collect(Collectors.toList()));
         }
         //log.info("token oidc oauth aaaaa {}", context.getPrincipal().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()));
      };

   }

   private Map<String, Object> extractClaims(Authentication principal) {
      Map<String, Object> claims;
      if (principal.getPrincipal() instanceof OidcUser) {
         log.info("token extractClaims oidc aaaaa {}", principal.getPrincipal());
         OidcUser oidcUser = (OidcUser) principal.getPrincipal();
         OidcIdToken idToken = oidcUser.getIdToken();
         claims = idToken.getClaims();
      } else if (principal.getPrincipal() instanceof OAuth2User) {
         log.info("token extractClaims oauth2 第三方登陆 aaaaa {}", principal.getPrincipal());
         OAuth2User oauth2User = (OAuth2User) principal.getPrincipal();
         claims = oauth2User.getAttributes();
         log.info("token extractClaims oauth3 第三方登陆 aaaaa {}", claims);
         if (oauth2User.getAttribute("provider") != null) {
            claims.put("zmhhxl_provider", oauth2User.getAttribute("provider"));
         }

      } else {
         claims = Collections.emptyMap();
      }

      return new HashMap<>(claims);
   }

   private static final Set<String> ID_TOKEN_CLAIMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
         IdTokenClaimNames.ISS,
         IdTokenClaimNames.SUB,
         IdTokenClaimNames.AUD,
         IdTokenClaimNames.EXP,
         IdTokenClaimNames.IAT,
         IdTokenClaimNames.AUTH_TIME,
         IdTokenClaimNames.NONCE,
         IdTokenClaimNames.ACR,
         IdTokenClaimNames.AMR,
         IdTokenClaimNames.AZP,
         IdTokenClaimNames.AT_HASH,
         IdTokenClaimNames.C_HASH
   )));

//   @Bean
//   public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() { // <3>
//      return (context) -> {
//         if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { // <4>
//            context.getClaims().claims((claims) -> { // <5>
//               Set<String> roles = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities())
//                     .stream()
//                     .map(c -> c.replaceFirst("^ROLE_", ""))
//                     ////.map(c -> c.replaceFirst("^SCOPE_", ""))
//                     .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); // <6>
////              Object claims1 =claims.get("scope");
////              claims.put("claims1",claims1);
//               claims.put("roles", roles); //<7>
//            });
//         }
//      };
//   }
}


